﻿namespace FsWeb.Controllers

open System;
open System.Collections.Generic;
open System.Configuration;
open System.Linq;
open System.Transactions;
open System.Web;
open System.Web.Mvc;
open System.Web.Security;
//open MongoDB.Driver.Builders
//open MongoDB.Bson
open DotNetOpenAuth.AspNet;
open Microsoft.Web.WebPages.OAuth;
//open NewBusiness.Domain.Uow.Data;
open WebMatrix.WebData;
open FsWeb.Models
//open NewBusiness.Domain.Uow.Data
open Business
open BusinessDomain
type AuthResponce = { errors : string list ;success : bool }

type ManageMessageId =
    |ChangePasswordSuccess = 0
    |SetPasswordSuccess = 1
    |RemoveLoginSuccess = 2

type ExternalLoginResult(provider:string, returnUrl:string) =
    inherit ActionResult()
    member val Provider =  provider with get
    member val ReturnUrl = returnUrl with get
    override u.ExecuteResult(context:ControllerContext) =
            OAuthWebSecurity.RequestAuthentication(provider, returnUrl);
    
        

[<Authorize>]
type AccountController(bc:IBusinessContext, dc:Data.DataContext) =
   inherit Controller()
   member val Context = bc with get

   member private  this.ViewBag s o = this.ViewData.Add(s,o);
   member private this.RouteValue s o = 
        let d = new Dictionary<string,System.Object>();
        d.Add(s,o);
        new Routing.RouteValueDictionary(d)

   member private this.RedirectToLocal(returnUrl:string) =   
    if this.Url.IsLocalUrl returnUrl then
        this.Redirect returnUrl :> ActionResult
    else
        this.RedirectToAction("Index", "Home") :> ActionResult


   member private this.GetErrorsFromModelState() =
      seq { for x in this.ModelState do
                yield! seq { for e in x.Value.Errors do yield e.ErrorMessage } } |> Seq.toList
   
   member private this.ErrorCodeToString (createStatus:MembershipCreateStatus) =
    match createStatus with 
    | MembershipCreateStatus.DuplicateUserName -> "Имя уже используется."
    | MembershipCreateStatus.DuplicateEmail -> "Этот email уже используется."
    | MembershipCreateStatus.InvalidPassword -> "Что-то не так с паролем. Введите пароль из 6 символов и более."
    | MembershipCreateStatus.InvalidEmail -> "Вы ввели не email, точно вам говорю."
    | MembershipCreateStatus.InvalidAnswer -> "Ответ должен быть какой-то другой."
    | MembershipCreateStatus.InvalidQuestion -> "Вопрос должен быть какой-то другой."
    | MembershipCreateStatus.InvalidUserName -> "Имя пользователя почему-то нам не нравится."
    | MembershipCreateStatus.ProviderError -> "Нашему сервису авторизации, что-то не понравилось. Поменяйте свои данные и попробуйте еще раз."
    | MembershipCreateStatus.UserRejected -> "Запрос для создания нового пользователя был отменен. Попробуйте еще раз."
    | _ -> "Случилось что-то не предвинное, скорее идите в бункер!"




       
//   [<AllowAnonymous>]
//   member this.Login (returnUrl:string) = 
//      this.ViewData.Add("returnUrl",returnUrl);
//      this.View() :> ActionResult
   
   [<HttpPost>]
   [<AllowAnonymous>]
   [<ValidateAntiForgeryToken>]
   member this.JsonLogin (model:FsWeb.Models.LoginModel, returnUrl:string) =
    if this.ModelState.IsValid then 
      try
        if WebSecurity.Login(model.UserName, model.Password, model.RememberMe) then
            FormsAuthentication.SetAuthCookie(model.UserName,model.RememberMe)
            this.Json({ errors = []; success = true })
        else
            this.ModelState.AddModelError("","Что-то не так с логином или паролем.")
            this.Json({ errors = this.GetErrorsFromModelState() ; success = false })
      with
        | :? InvalidOperationException as ex ->  this.ModelState.AddModelError("", ex.Message ) ; this.Json( {errors = this.GetErrorsFromModelState() ; success = false});
    else
        this.Json( {errors = this.GetErrorsFromModelState() ; success = false })

   [<HttpPost>]
   [<ValidateAntiForgeryToken>]
   member this.LogOff() = 
     WebSecurity.Logout()
     this.RedirectToAction("Index","Home") :> ActionResult

   [<AllowAnonymous>]
   member this.Register() =
     this.View() :> ActionResult

   [<HttpPost>]
   [<AllowAnonymous>]
   [<ValidateAntiForgeryToken>]
   member this.JsonRegister(model:FsWeb.Models.RegisterModel) = 
    if this.ModelState.IsValid then
        try
            WebSecurity.CreateUserAndAccount(model.UserName, model.Password) |> ignore;
            WebSecurity.Login( model.UserName,model.Password, true) |> ignore;
            FormsAuthentication.SetAuthCookie(model.UserName,model.RememberMe);
//            //TODO после написание простейшей орм исправить сие.
//            let user = MongoDB.Bson.BsonDocument().Add("_id", BsonObjectId(ObjectId())).Add("username", bstr model.UserName).Add("settings",BsonDocument())
//            (db |> getCollection "users").Insert(user) |> ignore
            this.Json({errors= []; success = true})
        with
            | :? MembershipCreateUserException as e -> this.ModelState.AddModelError("", this.ErrorCodeToString e.StatusCode ) ; this.Json( {errors = this.GetErrorsFromModelState() ; success = false});
    else
        this.Json( {errors = this.GetErrorsFromModelState() ; success = false});


   [<HttpPost>]
   //[<AllowAnonymous>]
   [<ValidateAntiForgeryToken>]
   member this.Disassociate (provider:string) (providerUserId:string) =
    //let mutable message:ManageMessageId
    let ownerAccount = OAuthWebSecurity.GetUserName(provider,providerUserId)
    let mutable message:System.Nullable<ManageMessageId> = System.Nullable();
    if ownerAccount = this.User.Identity.Name then
        use scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions ( IsolationLevel = IsolationLevel.Serializable ))
        let hasLocalAccount = OAuthWebSecurity.HasLocalAccount (WebSecurity.GetUserId this.User.Identity.Name);
        if hasLocalAccount || OAuthWebSecurity.GetAccountsFromUserName(this.User.Identity.Name).Count > 1 then
            OAuthWebSecurity.DeleteAccount(provider, providerUserId) |> ignore;
            scope.Complete();
            message <- new System.Nullable<ManageMessageId>(ManageMessageId.RemoveLoginSuccess);
    this.RedirectToAction("Manage", this.RouteValue "Message" message ) :>ActionResult
   

   [<HttpGet>]
   member this.Manage(message:System.Nullable<ManageMessageId>) = 
       let hasLocalPass = (this.User.Identity.Name |> WebSecurity.GetUserId |> OAuthWebSecurity.HasLocalAccount)
       this.ViewData.Add("HasLocalPassword", hasLocalPass);
       this.ViewData.Add("ReturnUrl", this.Url.Action("Manage"));
       if message.HasValue then 
          let ms = match message.Value with 
             |ManageMessageId.ChangePasswordSuccess -> "Ваш пароль был изменён."
             |ManageMessageId.SetPasswordSuccess -> "Вы создали пароль."
             |ManageMessageId.RemoveLoginSuccess -> "Успешно удалили вход через социалочку."
             | _ -> "Случилась нелепейшая ситуация. Что произошло совсем не понятно. Попробуйте еще раз."
          this.ViewData.Add("StatusMessage", ms);
       else
          ();
       //let k = this.ViewData
       this.PartialView(new LocalPasswordModel()) //:> Parti;

   [<HttpPost>]
   [<ValidateAntiForgeryToken>]
   member this.Manage(model:LocalPasswordModel) =
        let hasLocalAccount = this.User.Identity.Name |>WebSecurity.GetUserId |> OAuthWebSecurity.HasLocalAccount
        hasLocalAccount |> this.ViewBag "HasLocalPassword" 
        this.Url.Action("Manage") |> this.ViewBag "ReturnUrl"
        if hasLocalAccount then
            if this.ModelState.IsValid then
                let changePasswordSuccess = try WebSecurity.ChangePassword(this.User.Identity.Name , model.OldPassword, model.NewPassword) with | _ -> false
                if changePasswordSuccess then
                    this.RedirectToAction("Manage", this.RouteValue "Message" ManageMessageId.ChangePasswordSuccess) :> ActionResult
                else
                    this.ModelState.AddModelError("", "Текущий пароль был указан не верно или новый пароль не валиден")
                    this.View(model) :> ActionResult
            else 
                this.View(model) :> ActionResult
        else
            let state = this.ModelState.["OldPassword"]
            if state <> null then
                state.Errors.Clear()
            if this.ModelState.IsValid then
                try
                    WebSecurity.CreateAccount(this.User.Identity.Name, model.NewPassword) |> ignore
                    this.RedirectToAction("Manage", this.RouteValue "Message" ManageMessageId.SetPasswordSuccess) :> ActionResult
                with 
                | e -> this.ModelState.AddModelError("",e); this.View(model) :> ActionResult

            else
                this.View(model) :> ActionResult

   [<HttpPost>]
   [<AllowAnonymous>]
   [<ValidateAntiForgeryToken>]
   member this.ExternalLogin(provider, returnUrl:string) =
    new ExternalLoginResult(provider, this.Url.Action("ExternalLoginCallback", this.RouteValue "ReturnUrl" returnUrl)) :> ActionResult

   [<AllowAnonymous>]
   member this.ExternalLoginCallback(returnUrl) =
    let result = OAuthWebSecurity.VerifyAuthentication(this.Url.Action("ExternalLoginCallback", this.RouteValue "ReturnUrl" returnUrl ))
    if not result.IsSuccessful then
        this.RedirectToAction("ExternalLoginFailure") :> ActionResult
    else
        if OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, true) then
            this.RedirectToLocal(returnUrl)
        else
            if this.User.Identity.IsAuthenticated then
                OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, this.User.Identity.Name)
                this.RedirectToLocal(returnUrl)
            else
                let loginData = OAuthWebSecurity.SerializeProviderUserId(result.ProviderUserId,this.User.Identity.Name)
                OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName |> this.ViewBag "ProviderDisplayName" 
                returnUrl |> this.ViewBag "ReturnUrl"
                this.View("ExternalLoginConfirmation",new RegisterExternalLoginModel( UserName=result.UserName, ExternalLoginData=loginData )) :> ActionResult



   [<HttpPost>]
   [<AllowAnonymous>]
   [<ValidateAntiForgeryToken>]
   member this.ExternalLoginConfirmation(model:RegisterExternalLoginModel, returnUrl) =
    let (b,provider,providerUserId) = OAuthWebSecurity.TryDeserializeProviderUserId(model.ExternalLoginData);
    if this.User.Identity.IsAuthenticated || not b then
        this.RedirectToAction("Manage") :> ActionResult
    else 
        if this.ModelState.IsValid then
            if bc .UserContext.GetUser(this.User.Identity.Name) = null then
                bc.UserContext.CreateUser(new Data.UserProfile(UserName = model.UserName, Email = model.Email))
                OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName)
                OAuthWebSecurity.Login(provider,providerUserId, true) |> ignore
//                let user = MongoDB.Bson.BsonDocument().Add("_id", BsonObjectId(ObjectId())).Add("username", bstr model.UserName).Add("settings",BsonDocument())
//                (db |> getCollection "users").Insert(user) |> ignore
                this.RedirectToLocal(returnUrl)
            else
                this.ModelState.AddModelError("UserName", "Такое имя пользователя уже существует. Выберете другое")
                this.View(model) :> ActionResult
        else
            OAuthWebSecurity.GetOAuthClientData(provider).DisplayName |> this.ViewBag "ProviderDisplayName"
            returnUrl |> this.ViewBag "ReturnUrl"
            this.View(model) :> ActionResult

   [<AllowAnonymous>]
   member this.ExternalLoginFailure() =
        this.View() :> ActionResult
   
   [<AllowAnonymous>]
   [<ChildActionOnly>]
   member this.ExternalLoginsList(returnUrl) =
     returnUrl |> this.ViewBag "ReturnUrl"
     this.PartialView("_ExternalLoginsListPartial", OAuthWebSecurity.RegisteredClientData) :> ActionResult

   [<ChildActionOnly>]
   member this.RemoveExternalLogins() =
    let accounts = OAuthWebSecurity.GetAccountsFromUserName(this.User.Identity.Name)
    let externalLogins = accounts |> Seq.map (fun x -> 
                                let clientData = OAuthWebSecurity.GetOAuthClientData(x.Provider);
                                new ExternalLogin(Provider = x.Provider,
                                                    ProviderDisplayName = clientData.DisplayName,
                                                    ProviderUserId = x.ProviderUserId)) 
                        |> Seq.toArray
    (externalLogins.Count() > 1 || OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(this.User.Identity.Name)))
    |> this.ViewBag "ShowRemoveButton"
    this.PartialView("_RemoveExternalLoginsPartial", externalLogins) :> ActionResult
    